namespace WP7Contrib.Services.BingMaps.Model
{
    using System;
    using System.Device.Location;
    using Collections;
    using Common;

    public class LocationSearchCriterion : BaseModel, ICriterion<LocationSearchCriterion>,
                                                      IEquatable<LocationSearchCriterion>,
                                                      ICloneable<LocationSearchCriterion>,
                                                      ILocationSearchQueryCriterion,
                                                      ILocationSearchAddressCriterion,
                                                      ILocationSearchPointCriterion
    {
        private Address address;
        private GeoCoordinate point;
        private string query;
        private ObservableCollection<LocationEntity> includeEntities;

        public LocationSearchCriterion()
        {
            this.Reset();
        }

        public Address Address
        {
            get
            {
                return address;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.address, value, () => this.Address);
            }
        }

        public bool HasAddress
        {
            get {  return this.address != null && this.address != Address.Empty; }
        }

        public GeoCoordinate Point
        {
            get
            {
                return this.point;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.point, value, () => this.Point);
            }
        }

        public bool HasPoint
        {
            get { return this.point != null && this.point != GeoCoordinate.Unknown; }
        }

        public string Query
        {
            get
            {
                return this.query;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.query, value, () => this.Query);
            }
        }

        public bool HasQuery
        {
            get { return string.IsNullOrEmpty(this.query); }
        }

        public ObservableCollection<LocationEntity> IncludedEntities
        {
            get
            {
                return this.includeEntities;
            }
            set
            {
                this.SetPropertyAndNotify(ref this.includeEntities, value, () => this.IncludedEntities);
            }
        }

        public bool HasIncludedEntities
        {
            get { return this.includeEntities != null && this.includeEntities.Count != 0; }
        }

        public LocationSearchCriterion Reset()
        {
            this.address = new Address();
            this.point = new GeoCoordinate();
            this.query = null;

            this.includeEntities = new ObservableCollection<LocationEntity>();

            return this;
        }

        public static bool operator ==(LocationSearchCriterion lsc1, LocationSearchCriterion lsc2)
        {
            return Equals(lsc1, lsc2);
        }

        public static bool operator !=(LocationSearchCriterion lsc1, LocationSearchCriterion lsc2)
        {
            return !Equals(lsc1, lsc2);
        }

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj))
            {
                return false;
            }

            return obj is LocationSearchCriterion && this.Equals((LocationSearchCriterion)obj);
        }

        public override int GetHashCode()
        {
            return this.CombineHashCodes(this.Address, this.Point);
        }

        public bool Equals(LocationSearchCriterion criterion)
        {
            if (ReferenceEquals(null, criterion))
            {
                return false;
            }

            if (this.Address != criterion.Address)
            {
                return false;
            }

            if (this.Point != criterion.Point)
            {
                return false;
            }

            if (this.Query != criterion.Query)
            {
                return false;
            }

            return true;
        }

        ILocationSearchAddressCriterion ICloneable<ILocationSearchAddressCriterion>.ShallowClone()
        {
            return this.ShallowClone();
        }

        ILocationSearchPointCriterion ICloneable<ILocationSearchPointCriterion>.ShallowClone()
        {
            return this.ShallowClone();
        }

        ILocationSearchQueryCriterion ICloneable<ILocationSearchQueryCriterion>.ShallowClone()
        {
            return this.ShallowClone();
        }

        public LocationSearchCriterion ShallowClone()
        {
            var criterion = new LocationSearchCriterion
                                {
                                    Address = this.Address,
                                    Point = this.Point,
                                    Query = this.Query
                                };
            return criterion;
        }

        ILocationSearchAddressCriterion ICloneable<ILocationSearchAddressCriterion>.DeepClone()
        {
            return this.DeepClone();
        }

        ILocationSearchPointCriterion ICloneable<ILocationSearchPointCriterion>.DeepClone()
        {
            return this.DeepClone();
        }

        ILocationSearchQueryCriterion ICloneable<ILocationSearchQueryCriterion>.DeepClone()
        {
            return this.DeepClone();
        }

        public LocationSearchCriterion DeepClone()
        {
            var criterion = new LocationSearchCriterion
                                {
                                    Query = this.Query
                                };

            if (this.HasAddress)
                criterion.Address = this.Address.DeepClone();

            if (this.HasPoint)
                criterion.Point = new GeoCoordinate(this.Point.Latitude, this.Point.Longitude);

            return criterion;
        }
    }
}
